package tech.waterism.topo.engine;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import tech.waterism.topo.context.CalculationContext;
import tech.waterism.topo.model.*;

import java.util.*;

import static org.junit.jupiter.api.Assertions.*;

/**
 * DispatchEngine 测试类
 * 
 * <p>这是 calTopo() 方法替代实现的完整测试
 * 
 * @author waterism
 * @since 1.0.0
 */
class DispatchEngineTest {
    
    private DispatchEngine engine;
    private Date startTime;
    private Date observationStartTime;
    private Date modifyTime;
    
    @BeforeEach
    void setUp() {
        engine = new DispatchEngine();
        
        // 初始化时间参数
        Calendar cal = Calendar.getInstance();
        cal.set(2024, Calendar.JANUARY, 1, 0, 0, 0);
        observationStartTime = cal.getTime();
        
        cal.add(Calendar.HOUR, 6);
        startTime = cal.getTime();
        
        cal.add(Calendar.HOUR, 2);
        modifyTime = cal.getTime();
    }
    
    // ========== 基础功能测试 ==========
    
    @Test
    void testCalculate_EmptyNodes() {
        // 准备：空节点列表
        EngineRequest request = EngineRequest.builder()
                .startTime(startTime)
                .observationStartTime(observationStartTime)
                .modifyTime(modifyTime)
                .nodes(new ArrayList<>())
                .dispatchType(0)
                .outFlowType(0)
                .build();
        
        // 执行 & 验证：空节点列表应该抛出异常
        assertThrows(IllegalArgumentException.class, () -> engine.calculate(request));
    }
    
    @Test
    void testCalculate_SingleRiverNode() {
        // 准备：单个河道节点
        TopoNode node = createRiverNode("R001", "测试河道");
        
        EngineRequest request = EngineRequest.builder()
                .startTime(startTime)
                .observationStartTime(observationStartTime)
                .modifyTime(modifyTime)
                .nodes(Collections.singletonList(node))
                .dispatchType(0)
                .outFlowType(0)
                .build();
        
        // 执行
        EngineResponse response = engine.calculate(request);
        
        // 验证
        assertNotNull(response);
        assertTrue(response.getSuccess());
        assertEquals(1, response.getNodes().size());
        
        TopoNode resultNode = response.getNodes().get(0);
        assertNotNull(resultNode.getData());
        assertEquals("R001", resultNode.getStcd());
    }
    
    @Test
    void testCalculate_ReservoirNode() {
        // 准备：水库节点
        TopoNode reservoir = createReservoirNode("RR001", "测试水库");
        
        // Mock调度器
        NodeDispatcher dispatcher = (node, context) -> {
            // 模拟水库调度：设置出流
            node.getData().forEach(data -> data.setQOut(50.0));
        };
        
        EngineRequest request = EngineRequest.builder()
                .startTime(startTime)
                .observationStartTime(observationStartTime)
                .modifyTime(modifyTime)
                .nodes(Collections.singletonList(reservoir))
                .dispatchType(1)
                .outFlowType(1)
                .dispatcher(dispatcher)
                .build();
        
        // 执行
        EngineResponse response = engine.calculate(request);
        
        // 验证
        assertNotNull(response);
        assertTrue(response.getSuccess());
        
        TopoNode result = response.getNodes().get(0);
        assertNotNull(result.getData());
        // 验证调度器被调用
        assertTrue(result.getData().stream().allMatch(d -> d.getQOut() != null && d.getQOut() == 50.0));
    }
    
    // ========== 拓扑遍历测试 ==========
    
    @Test
    void testCalculate_MultipleNodes_Sequential() {
        // 准备：多个节点（顺序拓扑）
        TopoNode node1 = createRiverNode("R001", "上游");
        TopoNode node2 = createRiverNode("R002", "中游");
        TopoNode node3 = createRiverNode("R003", "下游");
        
        List<TopoNode> nodes = Arrays.asList(node1, node2, node3);
        
        EngineRequest request = EngineRequest.builder()
                .startTime(startTime)
                .observationStartTime(observationStartTime)
                .modifyTime(modifyTime)
                .nodes(nodes)
                .dispatchType(0)
                .outFlowType(0)
                .build();
        
        // 执行
        EngineResponse response = engine.calculate(request);
        
        // 验证：所有节点都被处理
        assertNotNull(response);
        assertTrue(response.getSuccess());
        assertEquals(3, response.getNodes().size());
    }
    
    @Test
    void testCalculate_WithUpstreamInput() {
        // 准备：上下游关系
        TopoNode upstream = createRiverNode("R001", "上游");
        upstream.getData().forEach(d -> d.setQSim(100.0));
        
        TopoNode downstream = createRiverNode("R002", "下游");
        // 设置输入分量
        InputComponent input = new InputComponent();
        input.setArcd("R001");
        input.setArtp("D");
        input.setData(createTimeSeriesData(5));
        downstream.setInputs(Collections.singletonList(input));
        
        List<TopoNode> nodes = Arrays.asList(upstream, downstream);
        
        EngineRequest request = EngineRequest.builder()
                .startTime(startTime)
                .observationStartTime(observationStartTime)
                .modifyTime(modifyTime)
                .nodes(nodes)
                .dispatchType(0)
                .outFlowType(0)
                .build();
        
        // 执行
        EngineResponse response = engine.calculate(request);
        
        // 验证：下游节点应该接收上游数据
        assertNotNull(response);
        assertTrue(response.getSuccess());
        assertEquals(2, response.getNodes().size());
    }
    
    // ========== 缓存机制测试 ==========
    
    @Test
    void testCalculate_ReuseCache() {
        // 准备：同一节点出现两次
        TopoNode node1 = createRiverNode("R001", "节点1");
        TopoNode node2 = createRiverNode("R001", "节点1"); // 相同站码
        
        List<TopoNode> nodes = Arrays.asList(node1, node2);
        
        EngineRequest request = EngineRequest.builder()
                .startTime(startTime)
                .observationStartTime(observationStartTime)
                .modifyTime(modifyTime)
                .nodes(nodes)
                .dispatchType(0)
                .outFlowType(0)
                .build();
        
        // 执行
        EngineResponse response = engine.calculate(request);
        
        // 验证：第二个节点应该复用缓存
        assertNotNull(response);
        assertTrue(response.getSuccess());
    }
    
    // ========== 联调测试 ==========
    
    @Test
    void testCalculate_JointDispatch_Simple() {
        // 准备：简单联调场景
        TopoNode upstream1 = createReservoirNode("RR001", "上游水库1");
        TopoNode upstream2 = createReservoirNode("RR002", "上游水库2");
        TopoNode joint1 = createReservoirNode("RR003", "联调水库1");
        TopoNode joint2 = createReservoirNode("RR004", "联调水库2");
        
        // 创建联调组
        JointDispatchGroup group = new JointDispatchGroup();
        group.setJmsid(1);
        group.setUpstreamNodes(Arrays.asList("RR001", "RR002"));
        group.setNodes(Arrays.asList(joint1, joint2));
        
        List<TopoNode> nodes = Arrays.asList(upstream1, upstream2, joint1, joint2);
        
        // Mock调度器
        NodeDispatcher dispatcher = (node, context) -> {
            node.getData().forEach(data -> data.setQOut(30.0));
        };
        
        EngineRequest request = EngineRequest.builder()
                .startTime(startTime)
                .observationStartTime(observationStartTime)
                .modifyTime(modifyTime)
                .nodes(nodes)
                .jointGroups(Collections.singletonList(group))
                .dispatchType(1)
                .outFlowType(1)
                .dispatcher(dispatcher)
                .build();
        
        // 执行
        EngineResponse response = engine.calculate(request);
        
        // 验证：联调成功执行
        assertNotNull(response);
        assertTrue(response.getSuccess());
        assertEquals(4, response.getNodes().size());
    }
    
    @Test
    void testCalculate_JointDispatch_NotTriggered() {
        // 准备：联调条件未满足
        TopoNode upstream1 = createReservoirNode("RR001", "上游水库1");
        TopoNode joint1 = createReservoirNode("RR003", "联调水库1");
        
        // 创建联调组：需要RR001和RR002，但只有RR001
        JointDispatchGroup group = new JointDispatchGroup();
        group.setJmsid(1);
        group.setUpstreamNodes(Arrays.asList("RR001", "RR002")); // RR002不存在
        group.setNodes(Collections.singletonList(joint1));
        
        List<TopoNode> nodes = Arrays.asList(upstream1, joint1);
        
        EngineRequest request = EngineRequest.builder()
                .startTime(startTime)
                .observationStartTime(observationStartTime)
                .modifyTime(modifyTime)
                .nodes(nodes)
                .jointGroups(Collections.singletonList(group))
                .dispatchType(1)
                .outFlowType(1)
                .build();
        
        // 执行
        EngineResponse response = engine.calculate(request);
        
        // 验证：联调未触发，但计算正常完成
        assertNotNull(response);
        assertTrue(response.getSuccess());
    }
    
    // ========== 统计器测试 ==========
    
    @Test
    void testCalculate_WithStatistician() {
        // 准备
        TopoNode node = createRiverNode("R001", "测试河道");
        
        // Mock统计器
        final boolean[] statisticianCalled = {false};
        NodeStatistician statistician = (n, context) -> {
            statisticianCalled[0] = true;
        };
        
        EngineRequest request = EngineRequest.builder()
                .startTime(startTime)
                .observationStartTime(observationStartTime)
                .modifyTime(modifyTime)
                .nodes(Collections.singletonList(node))
                .dispatchType(0)
                .outFlowType(0)
                .statistician(statistician)
                .build();
        
        // 执行
        EngineResponse response = engine.calculate(request);
        
        // 验证：统计器被调用
        assertNotNull(response);
        assertTrue(response.getSuccess());
        assertTrue(statisticianCalled[0], "统计器应该被调用");
    }
    
    // ========== 调度类型测试 ==========
    
    @Test
    void testCalculate_NaturalOutflow() {
        // 准备：天然出流模式
        TopoNode reservoir = createReservoirNode("RR001", "测试水库");
        
        EngineRequest request = EngineRequest.builder()
                .startTime(startTime)
                .observationStartTime(observationStartTime)
                .modifyTime(modifyTime)
                .nodes(Collections.singletonList(reservoir))
                .dispatchType(0) // 天然出流
                .outFlowType(0)
                .build();
        
        // 执行
        EngineResponse response = engine.calculate(request);
        
        // 验证
        assertNotNull(response);
        assertTrue(response.getSuccess());
    }
    
    @Test
    void testCalculate_CurrentOutflow() {
        // 准备：当前出流模式
        TopoNode reservoir = createReservoirNode("RR001", "测试水库");
        
        EngineRequest request = EngineRequest.builder()
                .startTime(startTime)
                .observationStartTime(observationStartTime)
                .modifyTime(modifyTime)
                .nodes(Collections.singletonList(reservoir))
                .dispatchType(1) // 当前出流
                .outFlowType(1)
                .build();
        
        // 执行
        EngineResponse response = engine.calculate(request);
        
        // 验证
        assertNotNull(response);
        assertTrue(response.getSuccess());
    }
    
    // ========== 异常处理测试 ==========
    
    @Test
    void testCalculate_NullNodes() {
        // 准备：null节点列表
        EngineRequest request = EngineRequest.builder()
                .startTime(startTime)
                .observationStartTime(observationStartTime)
                .modifyTime(modifyTime)
                .nodes(null)
                .dispatchType(0)
                .outFlowType(0)
                .build();
        
        // 执行 & 验证：应该抛出异常
        assertThrows(Exception.class, () -> engine.calculate(request));
    }
    
    @Test
    void testCalculate_InvalidTimeRange() {
        // 准备：无效时间范围（startTime早于observationStartTime）
        TopoNode node = createRiverNode("R001", "测试河道");
        
        Calendar cal = Calendar.getInstance();
        cal.set(2024, Calendar.JANUARY, 1, 12, 0, 0);
        Date invalidStartTime = cal.getTime();
        
        cal.set(2024, Calendar.JANUARY, 1, 6, 0, 0);
        Date invalidObsTime = cal.getTime();
        
        EngineRequest request = EngineRequest.builder()
                .startTime(invalidStartTime)
                .observationStartTime(invalidObsTime)
                .modifyTime(modifyTime)
                .nodes(Collections.singletonList(node))
                .dispatchType(0)
                .outFlowType(0)
                .build();
        
        // 执行：应该能处理（或根据业务规则抛出异常）
        EngineResponse response = engine.calculate(request);
        
        // 验证
        assertNotNull(response);
    }
    
    // ========== 性能测试 ==========
    
    @Test
    void testCalculate_LargeTopology() {
        // 准备：大规模拓扑（100个节点）
        List<TopoNode> nodes = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            nodes.add(createRiverNode("R" + String.format("%03d", i), "节点" + i));
        }
        
        EngineRequest request = EngineRequest.builder()
                .startTime(startTime)
                .observationStartTime(observationStartTime)
                .modifyTime(modifyTime)
                .nodes(nodes)
                .dispatchType(0)
                .outFlowType(0)
                .build();
        
        // 执行
        long startMs = System.currentTimeMillis();
        EngineResponse response = engine.calculate(request);
        long endMs = System.currentTimeMillis();
        
        // 验证
        assertNotNull(response);
        assertTrue(response.getSuccess());
        assertEquals(100, response.getNodes().size());
        
        // 性能检查（应该在合理时间内完成）
        long duration = endMs - startMs;
        assertTrue(duration < 10000, "大规模计算应在10秒内完成，实际用时：" + duration + "ms");
    }
    
    // ========== 集成测试 ==========
    
    @Test
    void testCalculate_CompleteFlow() {
        // 准备：完整流程测试
        // 上游水库 -> 中游河道 -> 下游水库 -> 出口河道
        TopoNode upstream = createReservoirNode("RR001", "上游水库");
        TopoNode midRiver = createRiverNode("R001", "中游河道");
        TopoNode downstream = createReservoirNode("RR002", "下游水库");
        TopoNode outlet = createRiverNode("R002", "出口河道");
        
        List<TopoNode> nodes = Arrays.asList(upstream, midRiver, downstream, outlet);
        
        // Mock调度器和统计器
        NodeDispatcher dispatcher = (node, context) -> {
            if (node.getOutcd().startsWith("RR")) {
                node.getData().forEach(data -> data.setQOut(40.0));
            }
        };
        
        NodeStatistician statistician = (node, context) -> {
            // 模拟统计计算
        };
        
        EngineRequest request = EngineRequest.builder()
                .startTime(startTime)
                .observationStartTime(observationStartTime)
                .modifyTime(modifyTime)
                .nodes(nodes)
                .dispatchType(1)
                .outFlowType(1)
                .dispatcher(dispatcher)
                .statistician(statistician)
                .build();
        
        // 执行
        EngineResponse response = engine.calculate(request);
        
        // 验证
        assertNotNull(response);
        assertTrue(response.getSuccess());
        assertEquals(4, response.getNodes().size());
        
        // 验证水库出流被设置
        TopoNode upstreamResult = response.getNodes().get(0);
        assertTrue(upstreamResult.getData().stream().allMatch(d -> d.getQOut() == 40.0));
    }
    
    // ========== 辅助方法 ==========
    
    /**
     * 创建河道节点
     */
    private TopoNode createRiverNode(String stcd, String stnm) {
        TopoNode node = new TopoNode();
        node.setStcd(stcd);
        node.setStnm(stnm);
        node.setOutcd("D"); // 河道类型
        node.setClen(3600); // 时段长1小时
        node.setUnt("S"); // 秒
        node.setData(createTimeSeriesData(5));
        node.setInputs(new ArrayList<>());
        return node;
    }
    
    /**
     * 创建水库节点
     */
    private TopoNode createReservoirNode(String stcd, String stnm) {
        TopoNode node = new TopoNode();
        node.setStcd(stcd);
        node.setStnm(stnm);
        node.setOutcd("RR"); // 水库类型
        node.setClen(3600);
        node.setUnt("S");
        node.setData(createTimeSeriesData(5));
        node.setInputs(new ArrayList<>());
        
        // 添加水库特有属性
        List<CurvePoint> curveHs = new ArrayList<>();
        curveHs.add(new CurvePoint(100.0, 1000.0));
        curveHs.add(new CurvePoint(110.0, 2000.0));
        node.setCurveHs(curveHs);
        
        return node;
    }
    
    /**
     * 创建时序数据
     */
    private List<TimeSeriesData> createTimeSeriesData(int count) {
        List<TimeSeriesData> dataList = new ArrayList<>();
        Calendar cal = Calendar.getInstance();
        cal.setTime(startTime);
        
        for (int i = 0; i < count; i++) {
            TimeSeriesData data = new TimeSeriesData();
            data.setDt(cal.getTime());
            data.setQ(0.0);
            data.setQSim(0.0);
            data.setQIn(0.0);
            data.setQOut(0.0);
            data.setQDvr(0.0);
            data.setQDsc(0.0);
            dataList.add(data);
            
            cal.add(Calendar.HOUR, 1);
        }
        
        return dataList;
    }
}

